package com.ctgu.redislock03.utils.redisLock;

import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * Redis 分布式锁工具类
 * 采用 Lua 脚本保证解锁的原子性
 *
 * @author ALEX
 * @since 2022/10/11 16:21
 */
public class RedisLock implements Lock {

    private static final String LOCK_PREFIX = "lock:user:";
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setScriptText(
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "return redis.call('del', KEYS[1]) " +
                "else " +
                        "return 0 " +
                "end");
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

    private final String name;
    private final StringRedisTemplate redisTemplate;

    public RedisLock(String name, StringRedisTemplate redisTemplate) {
        this.name = name;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 生成当前线程唯一 ID
     */
    private String getThreadId() {
        return UUID.randomUUID().toString(true) + "-" + Thread.currentThread().getId();
    }

    /**
     * 尝试获取锁
     * @param timeoutSec 锁的超时时间（秒）
     * @return 是否获取成功
     */
    @Override
    public boolean lock(long timeoutSec) {
        Boolean success = redisTemplate.opsForValue()
                .setIfAbsent(LOCK_PREFIX + name, getThreadId(), timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    /**
     * 释放锁
     */
    @Override
    public void unlock() {
        redisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(LOCK_PREFIX + name), getThreadId());
    }
}
